package com.pcbly.web;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.BufferedInputStream;
import java.io.FileInputStream;

import java.util.UUID;

import javax.naming.InitialContext;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import com.pcbly.web.components.utils.ClassPathUtils;
import com.pcbly.web.components.utils.InputOutputStreamUtils;

public class OfficeHome {
	public static final String LICENSE_FILE = "office.license";
	public static final String CONFIG_FILE = "office_startup.xml";
	public static final String OFFICE_ROOT_KEY = "office.home";
	public static final String OFFICE_LOG_KEY = "office.logs";
	public static final String OFFICE_HOME_KEY = "office.instance.home";
	public static final String OFFICE_ROOT_ENV_KEY = "OFFICE_HOME";
	public static final String CLASSPATH_RESOURCE_ROOT = "resources";
	private static UUID nodeUuid = null;
	private static final String startupOfficeHomePath = getOfficeHomePath();
	private static final String startupOfficeRootPath = getOfficeRootPath();
	private static final String startupLogPath = getOfficeLogsPath();
	private static String effectiveOfficeHomePath;
	private static String effectiveOfficeRootPath;
	private static String effectiveOfficeLogPath;
	private static boolean initialization = false;
	private static boolean initialized = false;
	private static final Logger log = LogManager.getLogger(OfficeHome.class);

	public static String getStartupOfficeHomePath() {
		return startupOfficeHomePath;
	}

	public static String getStartupOfficeRootPath() {
		return startupOfficeRootPath;
	}

	public static String getStartupOfficeLogsPath() {
		return startupLogPath;
	}

	public static synchronized void setEffectiveOfficeHomePath(String path) {
		if (path != null) {
			File pathFile = new File(path);
			if (pathFile.exists() && pathFile.isDirectory()
					&& pathFile.canRead() && pathFile.canWrite())
				effectiveOfficeHomePath = path;
		} else {
			effectiveOfficeHomePath = null;
		}
	}

	public static synchronized void setEffectiveOfficeRootPath(String path) {
		if (path != null) {
			File pathFile = new File(path);
			if (pathFile.exists() && pathFile.isDirectory()
					&& pathFile.canRead() && pathFile.canWrite())
				effectiveOfficeRootPath = path;
		} else {
			effectiveOfficeRootPath = null;
		}
	}

	public static synchronized void setEffectiveOfficeLogsPath(String path) {
		if (path != null) {
			File pathFile = new File(path);
			if (pathFile.exists() && pathFile.isDirectory()
					&& pathFile.canRead() && pathFile.canWrite())
				effectiveOfficeLogPath = path;
		} else {
			effectiveOfficeLogPath = null;
		}
	}

	public static String getEnvironmentOfficeLogsPath() {
		Logger log = LogManager.getLogger(OfficeHome.class);
		String envPath = System.getProperty("office.logs");
		if (envPath == null || "".equals(envPath))
			try {
				InitialContext context = new InitialContext();
				envPath = (String) context.lookup("java:comp/env/office.logs");
			} catch (Exception e) {
			}
		if (envPath == null || "".equals(envPath)) {
			File root = getOfficeRoot();
			if (!root.exists()) {
				if (initialization)
					log
							.warn("Office root directory does not exist for log configuration. Reverting to OFFICE_HOME/logs.");
				StringBuilder buffer = new StringBuilder(getOfficeHomePath());
				buffer.append(File.separator).append("logs");
				envPath = buffer.toString();
			} else {
				if (initialization)
					log
							.warn((new StringBuilder())
									.append(
											"No explicit configuration of Office log path. Using system default of: '")
									.append(envPath).append("'.").toString());
				StringBuilder buffer = new StringBuilder(getOfficeRootPath());
				buffer.append(File.separator).append("var").append(
						File.separator).append("logs");
				envPath = buffer.toString();
			}
		}
		return envPath;
	}

	public static String getEnvironmentOfficeRootPath() {
		Logger log = LogManager.getLogger(OfficeHome.class);
		String envRootPath = System.getProperty("office.home");
		if (envRootPath == null || "".equals(envRootPath))
			try {
				InitialContext context = new InitialContext();
				envRootPath = (String) context
						.lookup("java:comp/env/office.home");
			} catch (Exception e) {
			}
		if (envRootPath == null || "".equals(envRootPath)) {
			envRootPath = System.getenv("OFFICE_HOME");
			if (envRootPath != null && !"".equals(envRootPath))
				log.info((new StringBuilder()).append(
						"Office root set from system property to '").append(
						envRootPath).append("'.").toString());
		}
		if (envRootPath == null || "".equals(envRootPath)) {
			StringBuilder buffer = new StringBuilder();
			if (System.getProperty("os.name", "Linux").indexOf("Windows") == -1)
				buffer.append(File.separator).append("usr").append(
						File.separator).append("local").append(File.separator)
						.append("office");
			else
				buffer.append("c:").append(File.separator).append("office");
			envRootPath = buffer.toString();
			log
					.warn((new StringBuilder())
							.append(
									"No explicit configuration of Office root path. Using system default of: '")
							.append(envRootPath).append("'.").toString());
		}
		return envRootPath;
	}

	public static String getEnvironmentOfficeHomePath() {
		String path = null;
		Logger log = LogManager.getLogger(OfficeHome.class);
		try {
			InitialContext context = new InitialContext();
			path = (String) context
					.lookup("java:comp/env/office.instance.home");
			if (path != null && log.isDebugEnabled())
				log.debug((new StringBuilder()).append(
						"Community home set from JNDI to '").append(path)
						.append("'.").toString());
		} catch (Exception e) {
		}
		if (path == null || "".equals(path)) {
			path = System.getProperty("office.instance.home");
			if (path != null && !"".equals(path) && log.isDebugEnabled())
				log.debug((new StringBuilder()).append(
						"Community home set from system property to '").append(
						path).append("'.").toString());
		}
		if (path == null || "".equals(path))
			try {
				InitialContext context = new InitialContext();
				path = (String) context.lookup("java:comp/env/officeHome");
				if (path != null && !"".equals(path) && initialization)
					log
							.warn((new StringBuilder())
									.append(
											"Community home set from legacy JNDI officeHome setting to '")
									.append(path)
									.append(
											"'. Please upate configuration to use ")
									.append("office.instance.home").toString());
			} catch (Exception e) {
			}
		if (path == null || "".equals(path)) {
			path = System.getProperty("officeHome");
			if (path != null && !"".equals(path) && initialization)
				log
						.warn((new StringBuilder())
								.append(
										"Communuity home set from legacy officeHome system property to '")
								.append(path)
								.append(
										"'. Please update configuration to use ")
								.append("office.instance.home").toString());
		}
		if (path == null || "".equals(path)) {
			File rootFilePath;
			if (effectiveOfficeRootPath != null)
				rootFilePath = new File(effectiveOfficeRootPath);
			else
				rootFilePath = new File(getEnvironmentOfficeRootPath());
			String name = System.getProperty("office.name", "sbs");
			StringBuilder buffer = new StringBuilder(rootFilePath
					.getAbsolutePath());
			buffer.append(File.separator).append("applications").append(
					File.separator).append(name).append(File.separator).append(
					"home");
			path = buffer.toString();
			if (initialization)
				log
						.warn((new StringBuilder())
								.append(
										"Attempting to use default community home value in absence of explicit configuration: '")
								.append(path).append("'.").toString());
		}
		return path;
	}

	public static synchronized void initialize() {
		ClassLoader threadLoader = Thread.currentThread()
				.getContextClassLoader();
		if (effectiveOfficeRootPath == null)
			effectiveOfficeRootPath = getEnvironmentOfficeRootPath();
		if (effectiveOfficeHomePath == null)
			effectiveOfficeHomePath = getEnvironmentOfficeHomePath();
		if (effectiveOfficeLogPath == null)
			effectiveOfficeLogPath = getEnvironmentOfficeLogsPath();
		// File root = getOfficeRoot();
		File home = getOfficeHome();
		if (!home.exists())
			try {
				home.mkdirs();
			} catch (Exception ex) {
				String message = "Failed to create home directory during initialization.";
				log.fatal(message, ex);
				throw new RuntimeException(message, ex);
			}
		boolean failure = false;
		if (!home.isDirectory()) {
			log.fatal("Configured community home is not a directory.");
			failure = true;
		}
		if (!home.canRead()) {
			log.fatal("Cannot read from community home directory.");
			failure = true;
		}
		if (!home.canWrite()) {
			log.fatal("Cannot write to community home directory.");
			failure = true;
		}
		if (failure)
			throw new RuntimeException((new StringBuilder()).append(
					"Unable to access configured community home at '").append(
					home.getAbsolutePath()).append(
					"'. The system cannot operate in this state.").toString());
		File config = getConfigurationFile();
		if (!config.exists())
			try {
				createOfficeStartupXmlFile(config);
			} catch (Exception ex) {
				log.warn("Failed to create application startup file.", ex);
				failure = true;
			}
		File stats = getStats();
		if (!stats.exists())
			try {
				stats.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create spelling directory at '").append(
						stats.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File spelling = getSpelling();
		if (!spelling.exists())
			try {
				spelling.mkdirs();
				ClassPathUtils.copyPath(threadLoader, "resources/spelling",
						spelling);
			} catch (Exception ex) {
				log
						.warn((new StringBuilder()).append(
								"Failed to create spelling directory at '")
								.append(spelling.getAbsolutePath())
								.append("'.").toString(), ex);
				failure = true;
			}
		File attachments = getAttachments();
		File attachmentCache = getAttachmentCache();
		File attachmentImages = getAttachmentImages();
		File attachmentImageCache = getAttachmentImageCache();
		if (!attachments.exists())
			try {
				attachments.mkdirs();
				ClassPathUtils.copyPath(threadLoader,
						"resources/images/attachments", attachmentImages);
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create attachment directory at '").append(
						attachments.getAbsolutePath()).append("'.").toString(),
						ex);
				failure = true;
			}
		if (!attachmentCache.exists())
			try {
				attachmentCache.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create attachment cache directory at '")
						.append(attachmentCache.getAbsolutePath()).append("'.")
						.toString(), ex);
				failure = true;
			}
		if (!attachmentImages.exists())
			try {
				attachmentImages.mkdirs();
				ClassPathUtils.copyPath(threadLoader,
						"resources/images/attachments", attachmentImages);
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create attachment images directory at '")
						.append(attachmentCache.getAbsolutePath()).append("'.")
						.toString(), ex);
				failure = true;
			}
		if (!attachmentImageCache.exists())
			try {
				attachmentImageCache.mkdirs();
			} catch (Exception ex) {
				log
						.warn(
								(new StringBuilder())
										.append(
												"Failed to create attachment image cache directory at '")
										.append(
												attachmentImageCache
														.getAbsolutePath())
										.append("'.").toString(), ex);
				failure = true;
			}
		File cache = getCache();
		if (!cache.exists())
			try {
				cache.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create cache directory at '").append(
						cache.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File www = getWww();
		if (!www.exists())
			try {
				www.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create www directory at '").append(
						www.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File geoIP = getGeoIP();
		if (!geoIP.exists())
			try {
				geoIP.mkdirs();
				ClassPathUtils.copyPath(threadLoader, "resources/geoip", geoIP);
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create GeoIP directory at '").append(
						geoIP.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File temp = getTemp();
		if (!temp.exists())
			try {
				temp.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create temp directory at '").append(
						temp.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File logs = getOfficeLogs();
		if (!logs.exists())
			try {
				logs.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create logs directory at '").append(
						logs.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File images = getImages();
		File imageCache = getImageCache();
		if (!images.exists())
			try {
				images.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create images directory at '").append(
						images.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		if (!imageCache.exists())
			try {
				imageCache.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create image cache directory at '").append(
						imageCache.getAbsolutePath()).append("'.").toString(),
						ex);
				failure = true;
			}
		File etc = getEtc();
		if (!etc.exists())
			try {
				etc.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create etc directory at '").append(
						etc.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File resources = getResources();
		if (!resources.exists())
			try {
				resources.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create resources directory at '").append(
						resources.getAbsolutePath()).append("'.").toString(),
						ex);
				failure = true;
			}
		File blogs = getBlogs();
		if (!blogs.exists())
			try {
				blogs.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create blogs directory at '").append(
						blogs.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File blogImports = getBlogImports();
		if (!blogImports.exists())
			try {
				blogImports.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create blog imports directory at '").append(
						blogImports.getAbsolutePath()).append("'.").toString(),
						ex);
				failure = true;
			}
		File plugins = getPlugins();
		if (!plugins.exists())
			try {
				plugins.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create plugins directory at '").append(
						plugins.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File search = getSearch();
		if (!plugins.exists())
			try {
				search.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create search directory at '").append(
						search.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File crypto = getCrypto();
		if (!crypto.exists())
			try {
				crypto.mkdirs();
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create crypto directory at '").append(
						crypto.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		File themes = getThemes();
		if (!themes.exists())
			try {
				themes.mkdirs();
				ClassPathUtils.copyPath(threadLoader, "resources/themes",
						themes);
			} catch (Exception ex) {
				log.warn((new StringBuilder()).append(
						"Failed to create themes directory at '").append(
						themes.getAbsolutePath()).append("'.").toString(), ex);
				failure = true;
			}
		try {
			File nodeIdFile = new File(getOfficeHome(), "node.id");
			if (!nodeIdFile.exists()) {
				log.info("Generating new node UUID.");
				nodeIdFile.createNewFile();
				UUID nodeUuid = UUID.randomUUID();
				java.io.InputStream in = new ByteArrayInputStream(nodeUuid
						.toString().getBytes());
				OutputStream out = new FileOutputStream(nodeIdFile);
				InputOutputStreamUtils wrapper = new InputOutputStreamUtils(
						1024, true, in, new OutputStream[] { out });
				wrapper.doWrap();
				log.info("New node UUID persisted.");
			} else {
				log.info("Node UUID already present, skipping generation.");
			}
		} catch (Exception e) {
			log.warn("Unexpected error building node UUID.", e);
			failure = true;
		}
		if (failure)
			log
					.warn("Initialization failures encountered. Please check application logs for details. The system may be unstable.");
		else
			log.info("Initialization completed sucessfully.");
		initialized = true;
	}

	private static void createOfficeStartupXmlFile(File startupFile)
			throws IOException {
		FileWriter jhWriter = new FileWriter(startupFile);
		jhWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		jhWriter.write("<office>\n");
		jhWriter.write("    <setup>false</setup>\n");
		jhWriter.write("</office>\n");
		jhWriter.flush();
		jhWriter.close();
	}

	public static File getEtc() {
		return new File(getOfficeHome(), "etc");
	}

	public static File getWww() {
		return new File(getOfficeHome(), "www");
	}

	public static File getCache() {
		return new File(getOfficeHome(), "cache");
	}

	public static File getPlugins() {
		return new File(getOfficeHome(), "plugins");
	}

	public static File getAttachments() {
		return new File(getOfficeHome(), "attachments");
	}

	public static File getAttachmentCache() {
		return new File(getAttachments(), "cache");
	}

	public static File getAttachmentImages() {
		return new File(getImages(), "attachments");
	}

	public static File getAttachmentImageCache() {
		return new File(getAttachments(), "images");
	}

	public static File getBlogs() {
		return new File(getOfficeHome(), "blogs");
	}

	public static File getBlogImports() {
		return new File(getBlogs(), "imports");
	}

	public static File getCrypto() {
		return new File(getOfficeHome(), "crypto");
	}

	public static File getImages() {
		return new File(getOfficeHome(), "images");
	}

	public static File getImageCache() {
		return new File(getImages(), "cache");
	}

	public static File getResources() {
		return new File(getOfficeHome(), "resources");
	}

	public static File getSpelling() {
		return new File(getOfficeHome(), "spelling");
	}

	public static File getGeoIP() {
		return new File(getOfficeHome(), "geoip");
	}

	public static File getSearch() {
		return new File(getOfficeHome(), "search");
	}

	public static File getStats() {
		return new File(getOfficeHome(), "stats");
	}

	public static File getTemp() {
		return new File(getOfficeHome(), "temp");
	}

	public static File getThemes() {
		return new File(getOfficeHome(), "themes");
	}

	public static File getConfigurationFile() {
		return new File(getOfficeHome(), "office_startup.xml");
	}

	public static File getLicenseFile() {
		return new File(getOfficeHome(), "office.license");
	}

	public static File getOfficeLogs() {
		return new File(getOfficeLogsPath());
	}

	public static String getOfficeLogsPath() {
		if (effectiveOfficeLogPath != null) {
			return effectiveOfficeLogPath;
		} else {
			effectiveOfficeLogPath = getEnvironmentOfficeLogsPath();
			return effectiveOfficeLogPath;
		}
	}

	public static File getOfficeHome() {
		return new File(getOfficeHomePath());
	}

	public static String getOfficeHomePath() {
		if (effectiveOfficeHomePath != null) {
			return effectiveOfficeHomePath;
		} else {
			effectiveOfficeHomePath = getEnvironmentOfficeHomePath();
			return effectiveOfficeHomePath;
		}
	}

	public static File getOfficeRoot() {
		return new File(getOfficeRootPath());
	}

	public static String getOfficeRootPath() {
		if (effectiveOfficeRootPath != null) {
			return effectiveOfficeRootPath;
		} else {
			effectiveOfficeRootPath = getEnvironmentOfficeRootPath();
			return effectiveOfficeRootPath;
		}
	}

	public static String getOfficeLogPath() {
		if (effectiveOfficeLogPath != null) {
			return effectiveOfficeLogPath;
		} else {
			effectiveOfficeLogPath = getEnvironmentOfficeLogsPath();
			return effectiveOfficeLogPath;
		}
	}

	public static boolean isManagedInstall() {
		File root = getOfficeRoot();
		if (!root.exists() || !root.isDirectory())
			return getOfficeLogs().exists() && getOfficeHome().exists();
		else
			return true;
	}

	public static UUID getNodeID() {
		try {
			if (nodeUuid == null) {
				File nodeIdFile = new File(getOfficeHome(), "node.id");
				java.io.InputStream is = new BufferedInputStream(
						new FileInputStream(nodeIdFile));
				ByteArrayOutputStream out = new ByteArrayOutputStream();
				InputOutputStreamUtils wrapper = new InputOutputStreamUtils(
						1024, true, is, new OutputStream[] { out });
				wrapper.doWrap();
				nodeUuid = UUID.fromString(new String(out.toByteArray()));
			}
			return nodeUuid;
		} catch (Exception e) {
			log.warn("Unexpected error retrieving node UUID.", e);
			return null;
		}
	}

	public static boolean isInitialized() {
		return initialized;
	}

	private OfficeHome() {
	}

}
